package com.jl.registermvc;

import com.jl.annotation.JLMvcApp;
import com.jl.util.ClassUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo.BuilderConfiguration;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Set;

/**
 * 这个是第三步 进行绑定url
 *
 * @author kelvin
 */
@Slf4j
public class ContractAutoHandlerRegisterHandlerMapping extends RequestMappingHandlerMapping {

    Set<String> basePackages;

    private BuilderConfiguration mappingInfoBuilderConfig;
    private boolean isGetSupperClassConfig = false;

    public ContractAutoHandlerRegisterHandlerMapping(Set<String> basePackages) {
        super();
        this.basePackages = basePackages;
        // 这里为了能够比SimpleMappingHandlerMapping早点使用，不然会匹配/** 的url
        super.setOrder(super.getOrder() - 1);
    }

    /**
     * 判断是否符合触发自定义注解的实现类方法
     */
    @SneakyThrows
    @Override
    protected boolean isHandler(Class<?> beanType) {
        // 传进来的可能是接口，比如 FactoryBean 的逻辑
        if (beanType.isInterface()) {
            return false;
        }

        // 是否是Contract的代理类，如果是则不支持
        if (ClassUtil.isContractTargetClass(beanType)) {
            return false;
        }

        if (basePackages == null || basePackages.size() == 0) {
            // 是否有标注了 @JLMvc
            Class<?> contractMarkClass = ClassUtil.getContractMarkClass(beanType);
            return contractMarkClass != null;
        } else {
            //跳过启动类
            String name = beanType.getName();
            name = name.indexOf("$$") != -1 ? name.substring(0, name.indexOf("$$")) : name;
            boolean annotationPresent = Class.forName(name).isAnnotationPresent(JLMvcApp.class);
            if (annotationPresent) {
                return false;
            }
            // 是否在包范围内，如果不在则不支持
            return isPackageInScope(beanType);
        }
    }

    /**
     * 扫到实现类的方法时，与这里返回的url进行板顶
     */
    @Override
    protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
        try {
            //获取接口
            Class<?> contractMarkClass = handlerType.getInterfaces()[0];
            Method fmethod = contractMarkClass.getMethod(method.getName(), method.getParameterTypes());
            //判断是否加了@springmvc路由注解
            if (method.isAnnotationPresent(GetMapping.class)
                    || method.isAnnotationPresent(PostMapping.class)
                    || method.isAnnotationPresent(PutMapping.class)
                    || method.isAnnotationPresent(DeleteMapping.class)
                    || method.isAnnotationPresent(PatchMapping.class)
                    || fmethod.isAnnotationPresent(GetMapping.class)
                    || fmethod.isAnnotationPresent(PostMapping.class)
                    || fmethod.isAnnotationPresent(PutMapping.class)
                    || fmethod.isAnnotationPresent(DeleteMapping.class)
                    || fmethod.isAnnotationPresent(PatchMapping.class)) {
                return null;
            }
            //封装请求
            RequestMappingInfo info = buildRequestMappingByMethod(method);
            if (info != null) {
                RequestMappingInfo typeInfo = buildRequestMappingByClass(contractMarkClass);
                if (typeInfo != null) {
                    info = typeInfo.combine(info);
                }
            }
            return info;
        } catch (Exception ex) {
            return null;
        }
    }

    private RequestMappingInfo buildRequestMappingByClass(Class<?> contractMarkClass) {

        String simpleName = contractMarkClass.getSimpleName();
        String[] paths = new String[]{simpleName};
        RequestMappingInfo.Builder builder = RequestMappingInfo.paths(resolveEmbeddedValuesInPatterns(paths));

        // 通过反射获得 config
        if (!isGetSupperClassConfig) {
            BuilderConfiguration config = getConfig();
            this.mappingInfoBuilderConfig = config;
        }

        if (this.mappingInfoBuilderConfig != null) {
            return builder.options(this.mappingInfoBuilderConfig).build();
        } else {
            return builder.build();
        }
    }

    private RequestMappingInfo buildRequestMappingByMethod(Method originalMethod) {
        // 用名字作为url
        // 有@RequestBody post形式,application/json请求 否则get形式,*/*请求
        String[] paths = new String[]{originalMethod.getName()};
        String consumes = MediaType.ALL_VALUE;
        RequestMethod methods = RequestMethod.GET;
        Parameter[] parameters = originalMethod.getParameters();
        if (parameters.length > 0) {
            RequestBody annotation = parameters[0].getAnnotation(RequestBody.class);
            if (annotation != null) {
                consumes = MediaType.APPLICATION_JSON_VALUE;
                methods = RequestMethod.POST;
            }
        }
        RequestMappingInfo.Builder builder = RequestMappingInfo.paths(resolveEmbeddedValuesInPatterns(paths))
                .consumes(consumes)
//                .params(requestMapping.params())
//                .mappingName(name)
//                .headers("Content-Type=" + MediaType.APPLICATION_JSON_VALUE)
                .produces(MediaType.APPLICATION_JSON_VALUE)
                .methods(methods);
        return builder.options(this.getConfig()).build();
    }

    BuilderConfiguration getConfig() {
        Field field;
        BuilderConfiguration configChild = null;
        try {
            field = RequestMappingHandlerMapping.class.getDeclaredField("config");
            field.setAccessible(true);
            configChild = (BuilderConfiguration) field.get(this);
        } catch (IllegalArgumentException | IllegalAccessException e) {
            log.error(e.getMessage(), e);
        } catch (NoSuchFieldException | SecurityException e) {
            log.error(e.getMessage(), e);
        }
        return configChild;
    }

    /**
     * 判断指定类是否在包范围内
     *
     * @param beanType 指定类
     * @return 如果在范围内返回 true，否则返回 false
     */
    private boolean isPackageInScope(Class<?> beanType) {
        // 是否在包路径内
        String packageName = ClassUtils.getPackageName(beanType);
        boolean isPackageScope = false;
        for (String basePackage : basePackages) {
            if (packageName.startsWith(basePackage)) {
                isPackageScope = true;
                break;
            }
        }
        return isPackageScope;
    }

}
